package stud.mpgame.snapshot;

import stud.mpgame.network.Message;

class SnapshotManager {
	
	private var tickTime:Int;
	
	private var localSnapshot:ISnapshot;
	
    private var lastServerSnapshotTime:Int;
    private var nextServerSnapshots:Array<ISnapshot>;
    private var tempServerSnapshot:ISnapshot;

    private var nextApplyCommandTime:Int;
    private var commands:Array<ICommand>;	
		
	public function new(serverTickTime:Int, localSS:ISnapshot) {
		tickTime = serverTickTime;
		localSnapshot = localSS;
		
		lastServerSnapshotTime = 0;
		nextServerSnapshots = new Array<ISnapshot>();
		tempServerSnapshot = null;
		
		nextApplyCommandTime = localSnapshot.getTime() + tickTime;
		commands = new Array<ICommand>();
		
		_mode = 0;
	}
	
	public function addServerSnapshot(ss:ISnapshot):Void {
		var serverSSTime:Int = ss.getTime();
        if ( lastServerSnapshotTime > serverSSTime )
            return;

        lastServerSnapshotTime = serverSSTime;

        var localSSTime:Int = localSnapshot.getTime();
        if ( localSSTime >= serverSSTime )
        {
            localSnapshot.synchronize(ss);

            /**
             * В списке серверных снепшотов нет элементов, т.к. ss имеет самое большое время
             * из всех серверных снепшотов, и это время меньше времени локального снепшота.
             * Т.е. когда было достигнуто localSSTime > serverSSTime они были удалены в update().
             *
             * Экстраполируем серверный снепшот в будущее и будем к нему интерполироваться.
             */
            var deltaTime:Int = Std.int(localSSTime/tickTime + 1) * tickTime - serverSSTime;
            ss.extrapolate(deltaTime);
            tempServerSnapshot = ss;
        }
        else
        {
            tempServerSnapshot = null;
			
			//todo проверить что в списке нет снепшотов с этим же временем, если есть заменить.
            nextServerSnapshots.push(ss);

            nextServerSnapshots.sort(function(x:ISnapshot, y:ISnapshot):Int {
                return x.getTime() - y.getTime();
            });
        }
	}
	
	public function addCommand(command:ICommand):Void {
        commands.push(command);
	}
	
	public function getLocalSnapshotTime():Int {
		return localSnapshot.getTime();
	}
	
	private var _mode:Int;
	public function setMode(mode:Int):Void
	{
		_mode = mode;
		trace("mode: " + _mode);
	}
		
	public function update(deltaTime:Int):ISnapshot {
		if ( deltaTime <= 0 )
			return localSnapshot;
		
		var localTime = localSnapshot.getTime();
		var targetTime = localTime + deltaTime;
					
        var nextSnapshot:ISnapshot = null;
        if ( nextServerSnapshots.length == 0 )
        {
            if ( tempServerSnapshot != null && tempServerSnapshot.getTime() > localSnapshot.getTime() ) {
				nextSnapshot = tempServerSnapshot;
			}
            else
                tempServerSnapshot = null;
        }
        else
        {
			var toRemove = 0;
			for ( i in 0...nextServerSnapshots.length ) {
				var nextSS:ISnapshot = nextServerSnapshots[toRemove];
				
				var nextTime = nextSS.getTime();
				if ( nextTime >= targetTime )
				{
					nextSnapshot = nextSS;
					break;
				}
								
				if ( nextTime > localTime ) //но nextTime < localTime + deltaTime
				{
					//разбить время на два промежутка и промоделировать для каждого.
					var dt = nextTime - localTime; 
					update(dt);
					return update(deltaTime - dt);
				}
				
				if ( nextTime <= localTime ) {
					localSnapshot.synchronize(nextSS);					
					toRemove++;
				}
			}
						
			//не сумели подобрать
			if ( nextSnapshot == null && toRemove > 0 ) { 
				var lastServerSnapshot:ISnapshot = nextServerSnapshots[toRemove - 1];
				
				var dt:Int = targetTime - lastServerSnapshot.getTime();
				var n:Int = (( dt % tickTime > 0 ) ? 1 : 0) + Std.int(dt / tickTime);
				
				lastServerSnapshot.extrapolate(n * tickTime);
				nextSnapshot = tempServerSnapshot = lastServerSnapshot;
			}
			
			if ( toRemove > 0 )
				nextServerSnapshots.splice(0, toRemove);
			
			//nextSnapshot = nextSS;
            //nextSnapshot = getNextSnapshot(deltaTime, localSnapshot, nextServerSnapshots);
		}
		
        if ( nextSnapshot != null && _mode != 1)
			localSnapshot.interpolate(deltaTime, nextSnapshot);
        else if ( _mode != 2 )
            localSnapshot.extrapolate(deltaTime);
		
		if (targetTime >= nextApplyCommandTime) { //должны попасть по равно, засчет махинаций (пары update) в while()
			if ( _mode != 3 ) {
				for (command in commands) {
					command.apply(localSnapshot); //remove 	
				}
			}
			
			commands.splice(0, commands.length);
			var num = Std.int( (localTime - nextApplyCommandTime) / tickTime ) + 1;
			//var dt = localTime - nextApplyCommandTime;
			//var num = (( dt % tickTime > 0 ) ? 1 : 0) + Std.int(dt / tickTime);
			nextApplyCommandTime += num * tickTime;
		}
			
		return localSnapshot;
	}
	
	public function getLocalSnapshot():ISnapshot {
		return localSnapshot;
	}
	
	public function getNextApplyCommandTime():Int {
		return nextApplyCommandTime;
	}
}